package made.ga;

import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.ArrayList;
import java.util.Random;

import made.util.Config;
import made.util.Logger;
import made.util.Config.DEBUG_LEVEL;

public class GeneticAlgorithm implements Config
{
    protected ArrayList<ChromosomeInt> _population;
    protected FitnessComparatorInt _fitnessComparator;
    protected String _name;
    protected int _chromosomeSize;
    protected int _populationSize;
    protected int _bestChromosome;
    protected boolean _converged;
    protected int[] _previousGeneSum;

    // FitnessEvaluator[] _evaluators;

    public GeneticAlgorithm(String name, int chromosomeSize, int populationSize)
    {
        init(name, chromosomeSize, populationSize);
    }

    protected void init(String name, int chromosomeSize, int populationSize)
    {
        Logger.logLine("* Genetic Algorithm started *");
        _name = name;
        _chromosomeSize = chromosomeSize;
        _populationSize = populationSize;
        _population = new ArrayList<ChromosomeInt>(_populationSize);
        _fitnessComparator = new FitnessComparatorInt();
        _converged = false;
        _bestChromosome = -1;
        _previousGeneSum = new int[CHROMOSOME_SIZE];

        for (int i = 0; i < _populationSize; i++)
        {
            _population.add(new ChromosomeInt(_chromosomeSize));
        }

        printPopulation();
    }

    public void setChromosomeFitness(int chromosomeIndex, int fitness)
    {
        // Logger.logLine("* Evaluating chromosome *");

        _population.get(chromosomeIndex).setFitness(fitness);
    }

    public void evaluateGeneration(int generation)
    {
        if (Config.MPM_DEBUG_LEVEL == DEBUG_LEVEL.VERBOSE)
            Logger.logLine("* Evaluating generation *");

        float nextAverageFitness = 0.0f;
        float minFitness = Float.POSITIVE_INFINITY;
        float maxFitness = Float.NEGATIVE_INFINITY;
        int worstChromosome = -1;

        for (int i = 0; i < _populationSize; i++)
        {
            float nextFitness = getChromosome(i).getFitness();
            nextAverageFitness += nextFitness;

            if (nextFitness < minFitness)
            {
                minFitness = nextFitness;
                worstChromosome = i;
            }

            if (nextFitness > maxFitness)
            {
                maxFitness = nextFitness;
                _bestChromosome = i;
            }
        }

        nextAverageFitness = nextAverageFitness / _populationSize;

        if (MPM_DEBUG_LEVEL == DEBUG_LEVEL.VERBOSE)
        {
            float deltaFitness = maxFitness - minFitness;
            Logger.logLine();
            Logger.logLine("Generation:\t" + generation);
            Logger.logLine("AvgFitness:\t" + nextAverageFitness);
            Logger.logLine("MinFitness:\t" + minFitness + " (" + worstChromosome + ")");
            Logger.logLine("MaxFitness:\t" + maxFitness + " (" + _bestChromosome + ")");
            Logger.logLine("DeltaFitness:\t" + deltaFitness);
            Logger.logLine();
        }

        if (MPM_DEBUG_LEVEL == DEBUG_LEVEL.SILENT && MPM_MODE == MODE.TRAIN)
        {
            Logger.log(" " + minFitness);
            Logger.log(" " + nextAverageFitness);
            Logger.log(" " + maxFitness);
            Logger.logLine();
        }

        if (MPM_DEBUG_LEVEL == DEBUG_LEVEL.VERBOSE)
            printPopulation();

        // if (nextAverageFitness > 2.999)
        // if (nextAverageFitness >= 5000)
        // if (maxFitness >= 25000)
        if (hasConverged())
        {
            Logger.logLine("Fitness goal reached!");

            for (int i = 0; i < _populationSize; i++)
            {
                Logger.logMethod();
                Logger.log(" " + i + ":\t");
                Logger.log(getChromosome(i).toString());
                Logger.log("\t Fitness: " + getChromosome(i).getFitness());
                Logger.logLine();
            }

            saveBestChromosome(_bestChromosome);
            _converged = true;
        }
    }

    public void produceNextGeneration()
    {
        if (Config.MPM_DEBUG_LEVEL == DEBUG_LEVEL.VERBOSE)
            Logger.logLine("* Creating new generation *");

        Random mutationProbabilityGenerator = new Random();
        Collections.sort(_population, _fitnessComparator);

        int eliteCount = Math.round(0.1f * POPULATION_SIZE);
        int loserCount = 2 * eliteCount;
        int loserOffset = POPULATION_SIZE - loserCount;

        if (MPM_DEBUG_LEVEL == DEBUG_LEVEL.VERBOSE)
            Logger.logLine("Best " + eliteCount + " individuals:");

        for (int i = 0; i < eliteCount; i++)
        {
            // Logger.logLine("ChromosomeInt " + i + ", fitness: " +
            // getChromosome(i).getFitness());

            Random rand = new Random();
            int parent1Index = rand.nextInt(10);
            int parent2Index = parent1Index;

            while (parent1Index == parent2Index)
                parent2Index = rand.nextInt(10);

            // Logger.log("Parent index 1: " + parent1Index +
            // " Parent index 2: " + parent2Index);

            ChromosomeInt[] newGenes = _population.get(parent1Index).reproduce(_population.get(parent2Index));

            if (mutationProbabilityGenerator.nextFloat() < MUTATION_PROBABILITY)
            {
                newGenes[0].mutate();
                newGenes[1].mutate();
            }

            // Logger.logLine("Inserting offspring into index " + (loserOffset +
            // i * 2) + " og " + (loserOffset + (i * 2 + 1)));
            _population.set(loserOffset + i * 2, newGenes[0]);
            _population.set(loserOffset + (i * 2 + 1), newGenes[1]);
        }
    }

    public int size()
    {
        return _population.size();
    }

    public boolean hasConverged()
    {
        boolean allIdentical = false;

        for (int i = 0; i < POPULATION_SIZE; i++)
        {
            for (int j = 0; j < CHROMOSOME_SIZE; j++)
            {
                if (_population.get(i).getGene(j) == _population.get(0).getGene(j))
                    continue;
                else
                    return false;
            }
        }
        
        return true;
        // int[] currentGeneSum = new int[CHROMOSOME_SIZE];
        //
        // for (int i = 0; i < POPULATION_SIZE; i++)
        // {
        // for (int j = 0; j < CHROMOSOME_SIZE; j++)
        // {
        // currentGeneSum[j] += _population.get(i).getGene(j);
        // }
        // }
        //
        // int[] geneDelta = new int[CHROMOSOME_SIZE];
        // int geneDeltaSum = 0;
        //
        // for (int i = 0; i < CHROMOSOME_SIZE; i++)
        // {
        // geneDeltaSum += currentGeneSum[i] - _previousGeneSum[i];
        // }
        //
        // Logger.logLine("GeneDeltaSum = " + geneDeltaSum);
        //
        // _previousGeneSum = currentGeneSum;
        //
        // if (Math.abs(geneDeltaSum) < 1)
        // return true;
        // else
        // return false;

        // return _converged;
    }

    public ChromosomeInt getChromosome(int index)
    {
        return _population.get(index);
    }

    public ChromosomeInt getBestChromosome(int index)
    {
        return _population.get(index);
    }

    public void saveBestChromosome(int index)
    {
        if (MPM_DEBUG_LEVEL == DEBUG_LEVEL.VERBOSE)
            Logger.logLine("* Saving decision tree configuration *\n");

        BufferedWriter bufferedWriter = null;

        try
        {
            bufferedWriter = new BufferedWriter(new FileWriter(filepath + "GA_" + _name + "_config.txt", false));

            if (MPM_DEBUG_LEVEL == DEBUG_LEVEL.VERBOSE)
                Logger.logLine("Saving " + _population.get(index).toString());

            bufferedWriter.write(_population.get(index).toString());
        } catch (FileNotFoundException ex)
        {
            ex.printStackTrace();
        } catch (IOException ex)
        {
            ex.printStackTrace();
        } finally
        {
            // Close the BufferedWriter
            try
            {
                if (bufferedWriter != null)
                {
                    bufferedWriter.flush();
                    bufferedWriter.close();
                }
            } catch (IOException ex)
            {
                ex.printStackTrace();
            }
        }
    }

    protected void printPopulation()
    {
        Logger.logLine("POPULATION:");

        for (int i = 0; i < _population.size(); i++)
        {
            Logger.logLine(" " + i + ":\t" + _population.get(i).toString());
        }

        Logger.logLine();
    }
};